package com.xingchi.tornado.storage.core.supports;

import com.xingchi.tornado.core.exception.ExceptionWrap;
import com.xingchi.tornado.storage.core.AbstractFileStorageService;
import com.xingchi.tornado.storage.core.config.StorageProperties;
import io.minio.BucketExistsArgs;
import io.minio.GetObjectArgs;
import io.minio.GetObjectResponse;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import io.minio.http.Method;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

import static com.xingchi.tornado.storage.core.exception.FileCode.DOWNLOAD_FILE_FAIL;
import static com.xingchi.tornado.storage.core.exception.FileCode.FILE_DELETE_FAIL;
import static com.xingchi.tornado.storage.core.exception.FileCode.FILE_UPLOAD_FAIL;

/**
 * minio文件存储服务实现
 *
 * @author xingchi
 * @date 2024/4/26 22:07
 */
@Slf4j
public class MinioFileStorageService extends AbstractFileStorageService {


    private final MinioClient minioClient;
    private final String bucketName;
    private final StorageProperties.MinioProperties minioProperties;

    public MinioFileStorageService(StorageProperties.MinioProperties minioProperties) {

        this.minioCheck(minioProperties);
        try {
            this.bucketName = minioProperties.getBucketName();
            this.minioProperties = minioProperties;

            this.minioClient = MinioClient.builder()
                    .endpoint(minioProperties.getEndpoint())
                    .credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey())
                    .build();

            BucketExistsArgs bucketExistsArgs = new BucketExistsArgs.Builder()
                    .bucket(bucketName)
                    .build();

            boolean exists = minioClient.bucketExists(bucketExistsArgs);
            if (!exists) {
                if (!minioProperties.isAuto()) {
                    log.warn("Minio bucket not exists. Please create a bucket '{}'", bucketName);
                    throw new RuntimeException("Minio bucket not exists.");
                } else {
                    MakeBucketArgs makeBucketArgs = new MakeBucketArgs.Builder()
                            .bucket(bucketName)
                            .build();
                    minioClient.makeBucket(makeBucketArgs);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("minio create exception", e);
        }
        log.info("minio file storage service init successfully!");
    }

    private void minioCheck(StorageProperties.MinioProperties minioProperties) {

        if (StringUtils.isBlank(minioProperties.getEndpoint())) {
            throw new RuntimeException("minio endpoint is blank!");
        }

        if (StringUtils.isBlank(minioProperties.getAccessKey()) || StringUtils.isBlank(minioProperties.getSecretKey())) {
            throw new RuntimeException("minio accessKey or secretKey is empty!");
        }

        if (StringUtils.isBlank(minioProperties.getBucketName())) {
            throw new RuntimeException("minio bucketName is empty!");
        }

    }

    /**
     * 根据文件的输入流上传文件
     *
     * @param is       文件的输入流
     * @param fileName 文件的完整名称
     * @return 文件在文件存储服务器上的路径唯一标识，在云存储中返回文件的对象名
     */
    @Override
    public String uploadFile(InputStream is, String fileName) {

        String path = this.getPath(fileName);

        try {
            Map<String, String> header = new HashMap<>();
            header.put("Content-Disposition", "attachment; filename*=UTF-8''" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20"));
            PutObjectArgs args = new PutObjectArgs.Builder()
                    .bucket(bucketName)
                    .object(replacePath(path))
                    .stream(is, is.available(), -1)
                    .headers(header)
                    .build();
            minioClient.putObject(args);
        } catch (Exception e) {
            ExceptionWrap.cast(FILE_UPLOAD_FAIL);
        }
        return path;
    }

    private String replacePath(String path) {
        if (path.startsWith("/")) {
            path = path.substring(1);
        }
        return path;
    }

    /**
     * 根据文件在文件服务器上的存储路径获取文件对应的字节书，注意文件过大可能会导致内存溢出问题
     *
     * @param path 文件的存储路径
     * @return 文件字节
     */
    @Override
    public byte[] getFileBytes(String path) {

        GetObjectArgs getObjectArgs = new GetObjectArgs.Builder()
                .bucket(bucketName)
                .object(replacePath(path))
                .build();
        try {
            GetObjectResponse object = minioClient.getObject(getObjectArgs);
            return IOUtils.toByteArray(object);
        } catch (Exception e) {
            log.warn("Failed to obtain file bytes. file: '{}'", path);
            return new byte[0];
        }
    }

    @Override
    public InputStream getInputStream(String path) {
        GetObjectArgs getObjectArgs = new GetObjectArgs.Builder()
                .bucket(bucketName)
                .object(replacePath(path))
                .build();
        try {
            return minioClient.getObject(getObjectArgs);
        } catch (Exception e) {
            log.warn("Failed to obtain file bytes. file: '{}'", path, e);
            ExceptionWrap.cast(DOWNLOAD_FILE_FAIL, e.getMessage());
        }
        return null;
    }

    /**
     * 获取文件的访问url
     *
     * @param path 文件的存储路径，在云存储中则为对应的对象名称
     * @return url
     */
    @Override
    public String getUrl(String path) {
        try {
            if (minioProperties.isEncrypt()) {
                GetPresignedObjectUrlArgs urlArgs = new GetPresignedObjectUrlArgs.Builder()
                        .bucket(bucketName)
                        .method(Method.GET)
                        .object(replacePath(path))
                        .expiry((int) minioProperties.getExpireTime().getSeconds())
                        .build();
                return minioClient.getPresignedObjectUrl(urlArgs);
                // return StringUtils.isEmpty(minioProperties.getDomain()) ? url.toString() : minioProperties.getDomain() + url.getPath() + "?" + url.getQuery();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return minioProperties.getDomain() + path;
    }

    /**
     * 删除文件
     *
     * @param path 文件的存储路径
     */
    @Override
    public void deleteFile(String path) {
        RemoveObjectArgs args = new RemoveObjectArgs.Builder()
                .bucket(bucketName)
                .object(replacePath(path))
                .build();
        try {
            minioClient.removeObject(args);
        } catch (Exception e) {
            ExceptionWrap.cast(FILE_DELETE_FAIL);
        }
    }

}
